﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Recognizer.Dollar;
using System.Windows;

namespace Segmentation.ShortStraw
{
    public class Utils
    {
        public static ArrayList GetCornerShortStraw(ArrayList resampledPoints)
        {
            ArrayList corners = new ArrayList(resampledPoints.Count);
            Corner corner;

            if (corners.Count <= 3) return null;

            //Initialization
            for (int i = 0; i < resampledPoints.Count; i++)
            {
                corner = new Corner((PointR)resampledPoints[i]);
                corners.Add(corner);
            }

            //the first point in the resampled points is corner in default???
            corner = (Corner)corners[0];
            corner.IsCorner = true;

            int W = 3;
            double straw = 0.0;
            double strawTotal = 0.0;
            for (int i = W; i < corners.Count - W; i++)
            {
                straw = Recognizer.Dollar.Utils.Distance(((Corner)corners[i - W]).Point, ((Corner)corners[i + W]).Point);
                strawTotal += straw;
                corner = ((Corner)corners[i]);
                corner.Straw = straw;
            }

            corner = (Corner)corners[1];
            corner.Straw = Recognizer.Dollar.Utils.Distance(((Corner)corners[0]).Point, ((Corner)corners[1 + W]).Point) * 2 *
                           W / (W + 1);
            strawTotal += corner.Straw;

            corner = (Corner)corners[2];
            corner.Straw = Recognizer.Dollar.Utils.Distance(((Corner)corners[0]).Point, ((Corner)corners[2 + W]).Point) * 2 *
                          W / (W + 2);
            strawTotal += corner.Straw;
            
            corner = (Corner)corners[corners.Count - 2];
            corner.Straw = Recognizer.Dollar.Utils.Distance(((Corner)corners[corners.Count - 1]).Point, ((Corner)corners[corners.Count - 2 - W]).Point) * 2 *
                          W / (W + 1);
            strawTotal += corner.Straw;

            corner = (Corner)corners[corners.Count - 3];
            corner.Straw = Recognizer.Dollar.Utils.Distance(((Corner)corners[corners.Count - 1]).Point, ((Corner)corners[corners.Count - 3 - W]).Point) * 2 *
                          W / (W + 2);
            strawTotal += corner.Straw;

            double t = (strawTotal / (corners.Count - 2 * W)) * 0.95;

            for (int i = W; i < resampledPoints.Count - W; i++)
            {
                double strawI = ((Corner)corners[i]).Straw;
                if (strawI < t)
                {
                    double localMin = double.MaxValue;
                    int localMinIndex = i;

                    while (i < resampledPoints.Count - W && strawI < t)
                    {
                        if (strawI < localMin)
                        {
                            localMin = strawI;
                            localMinIndex = i;
                        }
                        i++;
                    }
                    corner = ((Corner)corners[localMinIndex]);
                    corner.IsCorner = true;
                }
            }
            //the last point in the resampled points is corner in default
            corner = ((Corner)corners[resampledPoints.Count - 1]);
            corner.IsCorner = true;

            PostProcessCorners(resampledPoints, ref corners);

            //sharp noise deduction : adjacency corner reduce.
            for (int i = 2; i < corners.Count; i++)
            {
                if ((((Corner)corners[i - 1]).IsCorner && ((Corner)corners[i]).IsCorner))
                {
                    if (((Corner)corners[i - 1]).Straw < ((Corner)corners[i]).Straw)
                    {
                        ((Corner)corners[i]).IsCorner = false;
                    }
                }
                if (((Corner)corners[i - 2]).IsCorner && ((Corner)corners[i]).IsCorner)
                {
                    if (((Corner)corners[i - 2]).Straw < ((Corner)corners[i]).Straw)
                    {
                        ((Corner)corners[i]).IsCorner = false;
                    }
                }
            }
            //Ad-hoc start or end noise corners
            ArrayList realCorners = GetRealCornerList(corners);
            int firstCorner = GetCornerIndex(corners, (Corner)realCorners[0]);
            int secondCorner = GetCornerIndex(corners, (Corner)realCorners[1]);
            int lastSecondCorner = GetCornerIndex(corners, (Corner)realCorners[realCorners.Count - 2]);
            int lastFirstCorner = GetCornerIndex(corners, (Corner)realCorners[realCorners.Count - 1]);

            if (secondCorner - firstCorner < 5)
            {
                ((Corner)corners[secondCorner]).IsCorner = false;
            }

            if (lastFirstCorner - lastSecondCorner < 5)
            {
                ((Corner)corners[lastSecondCorner]).IsCorner = false;
            }

            return corners;
        }

        public static ArrayList GetCornersIStraw(ArrayList resampledPoints)
        {
            ArrayList corners = new ArrayList(resampledPoints.Count);
            Corner corner;

            if (corners.Count <= 3) return null;

            //Initialization
            for (int i = 0; i < resampledPoints.Count; i++)
            {
                corner = new Corner((PointR) resampledPoints[i]);
                corners.Add(corner);
            }

            //the first point in the resampled points is corner in default???
            corner = (Corner)corners[0];
            corner.IsCorner = true;

            int W = 3;
            double straw = 0.0;
            double strawTotal = 0.0;
            for (int i = W; i < corners.Count - W; i++)
            {
                straw = Recognizer.Dollar.Utils.Distance(((Corner)corners[i - W]).Point, ((Corner)corners[i + W]).Point);
                strawTotal += straw;
                corner = ((Corner) corners[i]);
                corner.Straw = straw;
            }

            corner = (Corner)corners[1];
            corner.Straw = Recognizer.Dollar.Utils.Distance(((Corner) corners[0]).Point, ((Corner) corners[1 + W]).Point)*2*
                           W/(W + 1);
            strawTotal += corner.Straw;

            corner = (Corner) corners[2];
            corner.Straw = Recognizer.Dollar.Utils.Distance(((Corner)corners[0]).Point, ((Corner)corners[2 + W]).Point) * 2 *
                          W / (W + 2);
            strawTotal += corner.Straw;

            corner = (Corner)corners[corners.Count-2];
            corner.Straw = Recognizer.Dollar.Utils.Distance(((Corner)corners[corners.Count-1]).Point, ((Corner)corners[corners.Count - 2- W]).Point) * 2 *
                          W / (W + 1);
            strawTotal += corner.Straw;

            corner = (Corner)corners[corners.Count - 3];
            corner.Straw = Recognizer.Dollar.Utils.Distance(((Corner)corners[corners.Count - 1]).Point, ((Corner)corners[corners.Count - 3 - W]).Point) * 2 *
                          W / (W + 2);
            strawTotal += corner.Straw;

            double t = (strawTotal/(corners.Count - 2 * W))*0.95;

            for (int i = W; i < resampledPoints.Count - W; i++)
            {
                double strawI = ((Corner) corners[i]).Straw;
                if (strawI < t)
                {
                    double localMin = double.MaxValue;
                    int localMinIndex = i;

                    while (i < resampledPoints.Count - W && strawI < t)
                    {
                        if (strawI < localMin)
                        {
                            localMin = strawI;
                            localMinIndex = i;
                        }
                        i++;
                    }
                    corner = ((Corner)corners[localMinIndex]);
                    corner.IsCorner = true;
                }
            }
            //the last point in the resampled points is corner in default
            corner = ((Corner) corners[resampledPoints.Count - 1]);
            corner.IsCorner = true;

            PostProcessCorners(resampledPoints, ref corners);

            //sharp noise deduction : adjacency corner reduce.
            for (int i = 2; i < corners.Count; i++)
            {
                if((((Corner)corners[i-1]).IsCorner && ((Corner)corners[i]).IsCorner))
                {
                    if(((Corner)corners[i-1]).Straw < ((Corner)corners[i]).Straw)
                    {
                        ((Corner) corners[i]).IsCorner = false;
                    }
                }
                if(((Corner)corners[i-2]).IsCorner && ((Corner)corners[i]).IsCorner)
                {
                    if (((Corner)corners[i - 2]).Straw < ((Corner)corners[i]).Straw)
                    {
                        ((Corner)corners[i]).IsCorner = false;
                    }
                }
            }
            //Ad-hoc start or end noise corners
            ArrayList realCorners = GetRealCornerList(corners);
            int firstCorner = GetCornerIndex(corners, (Corner)realCorners[0]);
            int secondCorner = GetCornerIndex(corners, (Corner)realCorners[1]);
            int lastSecondCorner = GetCornerIndex(corners, (Corner)realCorners[realCorners.Count - 2]);
            int lastFirstCorner = GetCornerIndex(corners, (Corner)realCorners[realCorners.Count-1]);

            if(secondCorner - firstCorner < 5)
            {
                ((Corner)corners[secondCorner]).IsCorner = false;
            }

            if(lastFirstCorner - lastSecondCorner < 5)
            {
                ((Corner)corners[lastSecondCorner]).IsCorner = false;
            }

            //Curve Detection
             realCorners = GetRealCornerList(corners);
            int shift = 12;

            int previousCornerIndex = GetCornerIndex(corners, (Corner)realCorners[0]);
            for (int i = 1; i < realCorners.Count - 1; i++)
            {
                Corner currentCorner = (Corner)realCorners[i];
                int currentCornerIndex = GetCornerIndex(corners, (Corner)realCorners[i]);
                int nextCornerIndex = GetCornerIndex(corners, (Corner) realCorners[i + 1]);
                int preDiff = currentCornerIndex - previousCornerIndex;
                int nextDiff = nextCornerIndex - currentCornerIndex;
                int startIndex, endIndex;

                if(preDiff < shift)
                {
                    startIndex = previousCornerIndex;

                }else
                {
                    startIndex = currentCornerIndex - shift;
                }

                if(nextDiff < shift)
                {
                    endIndex = nextCornerIndex;
                }
                else
                {
                    endIndex = currentCornerIndex + shift;
                }
                // compute angle1 (alpha)
                double angle1 = GetAngle(((Corner)corners[currentCornerIndex]).Point, ((Corner)corners[startIndex]).Point, ((Corner)corners[endIndex]).Point);
                // compute angle2 (beta)
                startIndex = currentCornerIndex - (int)Math.Ceiling((decimal)(currentCornerIndex - startIndex) / 3);
                endIndex = currentCornerIndex + (int)Math.Ceiling((decimal)(endIndex - currentCornerIndex) / 3);
                double angle2 = GetAngle(((Corner)corners[currentCornerIndex]).Point, ((Corner)corners[startIndex]).Point, ((Corner)corners[endIndex]).Point);

                // compute angle3 
                if (preDiff < 6) startIndex = currentCornerIndex - 1;
                else startIndex = currentCornerIndex - 2;
                if (nextDiff < 6) endIndex = currentCornerIndex + 1;
                else endIndex = currentCornerIndex + 2;
                double angle3 = GetAngle(((Corner)corners[currentCornerIndex]).Point, ((Corner)corners[startIndex]).Point, ((Corner)corners[endIndex]).Point);

                // check all the angles to decide whether the current corner is real
                if ((angle2 > 36 + 0.85 * angle1 && angle1 > 20 && angle3 > 80 + 0.55 * angle1)
                    || angle3 > 161
                    || ((preDiff < 3 || nextDiff < 3) && angle2 > 130))
                {
                    //notCorner = true;
                    currentCorner.IsCorner = false;
                }

                previousCornerIndex = currentCornerIndex;
            }

            return corners;
        }

        public static double GetAngle(PointR center, PointR p1, PointR p2)
        {
            Vector vector1 = new Vector(p1.X - center.X, p1.Y - center.Y);
            Vector vector2 = new Vector(p2.X - center.X, p2.Y - center.Y);
            Double angleBetween;

            // angleBetween is approximately equal to 0.9548
            //angleBetween = Vector.AngleBetween(vector1, vector2);

            vector1.Normalize();
            vector2.Normalize();
            // compute the angle
            angleBetween = Math.Acos(vector1.X * vector2.X + vector1.Y * vector2.Y);
            return angleBetween * 180 / Math.PI;

            return angleBetween;
        }

        private static int GetCornerIndex(ArrayList corners, Corner corner)
        {
            Corner temp;
            for(int i = 0; i < corners.Count ; i++)
            {
                temp = (Corner) corners[i];
                if(temp == corner)
                {
                    return i;
                }
            }
            return 0;
        }

        //points: resampled points
        //corners contain straw
        protected static void PostProcessCorners(ArrayList points,
            ref ArrayList corners)
        {
            bool cont = true;
            ArrayList realCorners = null;

            PointR cor1;
            PointR cor2;

            //first add missing corners
            do
            {
                cont = true;
                realCorners = GetRealCornerList(corners);

                for (int i = 1; i < realCorners.Count; i++)
                {
                    cor1 = ((Corner)realCorners[i - 1]).Point;
                    cor2 = ((Corner)realCorners[i]).Point;
                    bool lineTest = IsLine(points, cor1, cor2);
                    if (!lineTest)
                    {
                        int newCornerIndex = GetHalfwayCornerIndex(corners, cor1, cor2);
                        var temp = ((Corner)corners[newCornerIndex]);
                        temp.IsCorner = true;
                        cont = false;
                    }
                }
            } while (!cont);

            //Second: remove false positive corners
            do
            {
                cont = false;
                realCorners = GetRealCornerList(corners);
                for (int j = 1; j < realCorners.Count - 1; j++)
                {
                    cor1 = ((Corner)realCorners[j - 1]).Point;
                    cor2 = ((Corner)realCorners[j + 1]).Point;
                    if (IsLine(points, cor1, cor2))
                    {
                        RemoveCorner(ref corners, (Corner) realCorners[j]);
                        cont = true;
                        break;
                    }
                }    
            } while (cont);
                        
        }

        //the parameter corners contain Corner
        protected static ArrayList GetRealCornerList(ArrayList corners)
        {
            ArrayList result = new ArrayList();
            Corner corner;
            for (int i = 0; i < corners.Count; i++)
            {
                corner = (Corner)corners[i];
                if (corner.IsCorner)
                {
                    result.Add((corner));
                }
            }
            return result;
        }

        //the parameter points contain PointR
        protected static bool IsLine(ArrayList points, PointR a, PointR b)
        {
            double threshold = 0.95;
            double distance = Recognizer.Dollar.Utils.Distance(a, b);
            double pathDistance = PathDistance(points, a, b);
            if (distance / pathDistance > threshold)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        //the parameter points contain PointR
        protected static double PathDistance(ArrayList points, PointR a, PointR b)
        {
            int aIndex = 0;
            int bIndex = 0;
            PointR temp;
            for (int i = 0; i < points.Count; i++)
            {
                temp = (PointR)points[i];
                if (temp.Equals(a))
                    aIndex = i;
                if (temp.Equals(b))
                    bIndex = i;
            }

            double d = 0;
            for (int j = aIndex; j <= bIndex - 1; j++)
            {
                d += Recognizer.Dollar.Utils.Distance((PointR)points[j], (PointR)points[j + 1]);
            }
            return d;
        }

        //the parameter corners contain all the points Corner, not real corner arrayList
        private static int GetHalfwayCornerIndex(ArrayList corners, PointR a, PointR b)
        {
            int aIndex = 0;
            int bIndex = 0;
            PointR temp;
            for (int i = 0; i < corners.Count; i++)
            {
                temp = ((Corner)corners[i]).Point;
                if (temp.Equals(a))
                    aIndex = i;
                if (temp.Equals(b))
                    bIndex = i;
            }

            int quarter = (bIndex - aIndex) / 2;
            double minValue = double.MaxValue;
            int minIndex = 0;
            for (int i = aIndex + quarter; i <= bIndex - quarter; i++)
            {
                double straw = ((Corner)corners[i]).Straw;
                if (straw < minValue)
                {
                    minValue = straw;
                    minIndex = i;
                }
            }
            return minIndex;
        }

        protected static void RemoveCorner(ref ArrayList corners, Corner corner)
        {
            Corner temp;
            for (int i = 0; i < corners.Count; i++)
            {
                temp = (Corner)corners[i];
                if (corner.Equals(temp))
                {
                    if (temp.IsCorner)
                    {
                        temp.IsCorner = false;
                    }
                }
            }
        }
    }
}